package cloudspace.vm.io.console;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.zkoss.zhtml.Br;
import org.zkoss.zhtml.Div;
import org.zkoss.zul.Label;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;

import cloudspace.vm.VM;

public class ConsoleLines extends Div
{
	private boolean autoScroll;
	private static final int MAX_CACHE_SIZE = 500;
	private Console vmConsole;
	private Map<String, StreamFormatter> streamFormatters;
	private boolean timeStampVis;
	private List<StreamLine> lineChildren;
	private GlobalStreamFilter globalFilter;

	private class ConsoleLinesListener implements CometEventListener
	{
		ConsoleLines componentLines;
		Console console;

		public ConsoleLinesListener(ConsoleLines userParent,
				Console consoleVM)
		{
			componentLines = userParent;
			console = consoleVM;
		}

		@Override
		public void checkEvent()
		{
			if (console.isDirty())
			{
				componentLines.notifyStreamUpdate();
			}

		}

	}

	private class GlobalStreamFilter
	{
		private boolean crossDesktopVis;

		public GlobalStreamFilter()
		{
			crossDesktopVis = false;
		}

		public boolean filterLine(InfoStreamLine isl)
		{
			String desktopID = Executions.getCurrent().getDesktop().getId();
			String taggedDesktopID = isl.getDesktopID();
			if (crossDesktopVis || taggedDesktopID == null || taggedDesktopID.equals(desktopID))
			{
				return true;
			}

			return false;
		}

		public void setDistributedVisibility(boolean remove)
		{
			crossDesktopVis = remove;

		}
	}

	private class StreamLine extends Label
	{
		private InfoStreamLine infoS;
		private Br lineBr;
		public StreamLine(InfoStreamLine isl, String text, String formatColor)
		{
			super(text);
			setStyle("color: " + formatColor);
			infoS = isl;
			lineBr = new Br();
		}
		@Override
		public boolean setVisible(boolean viz)
		{
			super.setVisible(viz);
			lineBr.setVisible(viz);
			return super.isVisible();
			
		}
		public String getStreamID()
		{
			return infoS.getStreamID();
		}

		public InfoStreamLine getInfoStreamLine()
		{
			return infoS;
		}
		public Br getBr()
		{
			return lineBr;
		}
	}

	private class StreamFormatter
	{
		String formatColor;
		boolean visible;

		public StreamFormatter(String color)
		{
			formatColor = color;
			visible = true;
		}

		public StreamLine createStreamComponent(InfoStreamLine isl)
		{
			String text = getFormatText(isl);
			StreamLine tempComponent = new StreamLine(isl, text, formatColor);
			tempComponent.setVisible(false);
			if (visible)
			{
				if (globalFilter.filterLine(isl))
				{
					tempComponent.setVisible(visible);
				}
			}
			return tempComponent;
		}

		public void updateStreamComponent(StreamLine sLine)
		{
			String text=
			getFormatText(sLine.getInfoStreamLine());
			sLine.setValue(text);
			sLine.setVisible(false);
			if (visible)
			{
				if (globalFilter.filterLine(sLine.getInfoStreamLine()))
				{
					sLine.setVisible(visible);
				}
			}
		}

		private String getFormatText(InfoStreamLine isl)
		{
			String text;
			if (timeStampVis)
			{
				text = isl.getLineWithTimestamp();
			}
			else
			{
				text = isl.getLine();
			}
			return text;
		}

		public void setVisibility(boolean flip)
		{
			visible = flip;
		}
	}

	public void onCreate()
	{
		init();
	}

	public void onExit()
	{
		Executions.getCurrent().getDesktop().enableServerPush(false);
	}

	public void init()
	{
		autoScroll = true;
		timeStampVis = false;
		lineChildren = new LinkedList<StreamLine>();
		globalFilter = new GlobalStreamFilter();
		streamFormatters = new HashMap<String, StreamFormatter>();
		vmConsole = new Console(VM.getThreadTag().getStdOut(), VM.getThreadTag()
				.getError());
		streamFormatters.put(vmConsole.getSystemOutStream().getStreamID(),
				new StreamFormatter("black"));
		streamFormatters.put(vmConsole.getSystemErrorStream().getStreamID(),
				new StreamFormatter("red"));
		streamFormatters.put(vmConsole.getSystemLogStream().getStreamID(),
				new StreamFormatter("green"));

		Executions.getCurrent().getDesktop().enableServerPush(true);
		CometUpdateThread updateThread = (CometUpdateThread) Executions
				.getCurrent().getDesktop().getAttribute("cometThread");
		updateThread.addListener(new ConsoleLinesListener(this, vmConsole));

		Events.postEvent("onUpdate", getFellow("visToolbar"), vmConsole);
	}

	public synchronized void notifyStreamUpdate()
	{
		List<InfoStreamLine> infoLines = vmConsole.getNewOrderedLines();
		linkComponents(infoLines);
		this.invalidate();
		updateScrollBox();

	}

	private void updateScrollBox()
	{
		if (autoScroll)
			Clients
					.evalJavaScript("var objDiv = document.getElementById(\"consoleLines\");objDiv.scrollTop = objDiv.scrollHeight;");
	}

	public void linkComponents(List<InfoStreamLine> infoLines)
	{
		if (this.getChildren().size() / 2 + infoLines.size() > MAX_CACHE_SIZE)
		{
			int numToRemove = this.getChildren().size() / 2 + infoLines.size()
					- MAX_CACHE_SIZE;
			for (int i = 0; i < numToRemove * 2; i++)
			{
				this.getChildren().remove(0);
			}
		}
		for (InfoStreamLine line : infoLines)
		{
			StreamFormatter sFormatter = streamFormatters.get(line
					.getStreamID());
			StreamLine component = sFormatter.createStreamComponent(line);
			this.appendChild(component);
			lineChildren.add(component);
			this.appendChild(component.getBr());
		}
	}

	/**
	 * This notifies the Vbox that visibility has been changed on its children.
	 * 
	 * @param streamID
	 *            the stream id to change visibility on.
	 */
	public void notifyVisChange(String streamID, boolean flip)
	{
		streamFormatters.get(streamID).setVisibility(flip);
		updateChildren();
		updateScrollBox();
	}

	public void notifyDisVisChange(boolean remove)
	{
		globalFilter.setDistributedVisibility(remove);
		updateChildren();
		updateScrollBox();
	}
	public void notifyAutoScrollChange()
	{
		autoScroll = !autoScroll;

	}
	public void notifyTimeStampChange()
	{
		timeStampVis = !timeStampVis;
		updateChildren();

	}
	private void updateChildren()
	{
		for(StreamLine sl : lineChildren)
		{
			StreamFormatter sFormatter = streamFormatters.get(sl.getStreamID());
			sFormatter.updateStreamComponent(sl);
		}
		this.invalidate();

	}
}